之前对 Wide And Deep 模型看过一点文章,但是没有深入了解,这两天抽出时间来仔细看了下相关代码和资料,然后写点初步的总结,总体来说还是很有意思的想法,把深度学习突破了在图像和语言领域的限制,用到了以往机器学习的领域,并且取得了更好的结果。
核心思想是?
wide and deep 模型的核心思想是结合线性模型的记忆能力(memorization)和 DNN 模型的泛化能力(generalization),在训练过程中同时优化两个模型的参数,从而达到整体模型的预测能力最优。
模型的特征构建与以往的线性模型、神经网络模型比较有什么特点?
特征的种类如下:
(第一行是 contrib 模块的 API,下面一行是正式的 API)
1 | tf.contrib.layers.real_valued_column 或者 |
可以看到,特征的种类还是类似于以往的机器学习领域的特征,以往做过机器学习项目的会比较熟悉,与图像语音等领域的还是有较大区别。
离散的特征怎么传入神经网络模型?
连续的特征可以直接传入神经网络,但是离散的特征需要做处理之后再传入:
To feed sparse features into DNN models, wrap the column with
embedding_column
orone_hot_column
.one_hot_column
will create a dense boolean tensor with an entry for each possible value, and thus the computation cost is linear in the number of possible values versus the number of values that occur in the sparse tensor. Thus using a “one_hot_column” is only recommended for features with only a few possible values. For features with many possible values or for very sparse features,embedding_column
is recommended.
有两种方式进行处理,可以使用 one-hot 编码处理神经网络输入的离散特征,也可以使用 embedding 处理;
建议该特征的数值种类较少时候使用 one-hot 编码,在该特征的数值种类较多的情况下使用 embedding 编码,这里之所以要这么处理,我理解的原因是独热编码如果数值种类很多,会导致编码后的特征维度太高,从而学习到的信息过于分散,因此,在种类很多的时候,需要先将超高维的种类离散特征进行压缩 embedding, 再送入神经网络;
那么,究竟什么是 embedding 处理呢?
什么是 embedding_column ? 什么时候使用?
1 | embedding_weights = variable_scope.get_variable( |
这是把离散特征 embedding 操作的核心,它把原始的类别数值映射到这个权值矩阵,其实相当于神经网络的权值,后续如果是 trainable 的话,我们就会把这个当做网络的权值矩阵进行训练,但是在用的时候,就把这个当成一个 embedding 表,按 id 去取每个特征的 embedding 后的数值。(这其实就类似于词向量了,把每个单词映射到一个词向量。)
什么是 sparseTensor? 什么时候使用这种数据类型?
它是用(位置,值,形状) 三个元素简略的表示一个矩阵的方法,例如:
1 | SparseTensor(indices=[[0, 0], [1, 2]], values=[1, 2], dense_shape=[3, 4]) |
表示如下矩阵:
1 | [[1, 0, 0, 0] |
也就是说它表明,在(0,0)的位置有 1, (1,2)的位置存在 2;
它只针对类别特征,适合于表示矩阵中大量元素为 0 的情况,会大大减少矩阵占用的存储量;在案例中,我发现它经常用来作为一个类别特征的中间变量矩阵,用来减小内存占用,例如:
这个代码中把所有的类别变量都用 SparseTensor 做了一个转换:
1 | categorical_cols = {k: tf.SparseTensor( |
值得注意的是,sparseTensor 本质上还是一个矩阵,它只是把矩阵用另外一种形式来表示了。
两个模型使用不同的优化器是什么? 怎么在 loss 层面配合?
以 tf.estimator.DNNLinearCombinedClassifier() 为例,进入该函数发现:linear_optimizer=’Ftrl’, 线性模型使用’Ftrl’优化器,
dnn_optimizer=’Adagrad’, 神经网络使用’Adagrad’优化器;
如果分类数量是 2,loss 使用
1 | _binary_logistic_head_with_sigmoid_cross_entropy_loss |
调用
1 | nn.sigmoid_cross_entropy_with_logits(labels=labels, logits=logits, name='loss') |
如果分类数量大于 2,loss 使用
1 | _multi_class_head_with_softmax_cross_entropy_loss |
调用
1 | losses.sparse_softmax_cross_entropy(labels=label_ids, |
最后怎么得到两个模型结合的预测结果的?
直接把两个模型的结果相加:
1 | logits = dnn_logits + linear_logits |
很出乎意料的方式啊,居然是直接相加的,然后是上面一点提到的操作,用这个 logits 和 label 得到 Loss,再将 Loss 分别反传回两个独立的优化器分别优化两个模型的权重。
两个模型各自使用的特征是什么?
先看代码:
离散特征和连续特征分别如下:
1 | # 类别特征 |
两个模块对特征的使用情况如下:
1 | wide_columns = [gender, native_country,education, occupation, workclass, relationship, age_buckets, |
wide 模型的特征都是离散特征、 离散特征之间的交互作用特征;
deep 模型的特征则是离散特征 embedding 加上连续特征;
wide 端模型和 deep 端模型只需要分别专注于擅长的方面,wide 端模型通过离散特征的交叉组合进行 memorization,deep 端模型通过特征的 embedding 进行 generalization,这样单个模型的大小和复杂度也能得到控制,而整体模型的性能仍能得到提高。
但是这种做法是不是一定要遵循,连续特征是否一定不要放入线性模型中去?我认为不一定,在以往的线性模型建模中,连续特征也是起到了很大作用的,所以可以在实践中进行尝试,不一定非得遵循这种做法。
几点疑问:
- 所有的输入到 deep 端 DNN 的连续特征都没有做归一化的处理?
- 报错 InternalError (see above for traceback): Blas GEMM launch failed : a.shape=(1282, 53), b.shape=(53, 100), m=1282, n=100, k=53 无法解决,网上查资料说是因为 GPU 内存不够,但即便把数据量调整到很小后还是报错,如果有人碰到这个问题的还希望留言答疑。
- 无法查看 bucketized_column,sparse_column_with_hash_bucket 等处理之后的特征数值,调用该 API 后只是定义了该特征列,无法看到它的实际数值。
相关代码:
1 | import tempfile |